home *** CD-ROM | disk | FTP | other *** search
/ Programmer Power Tools / Programmer Power Tools.iso / dirutl / update.c < prev    next >
C/C++ Source or Header  |  1989-08-26  |  18KB  |  597 lines

  1.  
  2. /*    update - copy new/altered files onto another disk with confirmation
  3.  
  4.     usage...
  5.         update  [options]  <source>  <destination> 
  6.         update  [options]  <destination> 
  7.         update  [options]  
  8.  
  9.         File names on the source and destination disks are
  10.         compared.  If a file on the source disk is newer (more
  11.         recent creation date) than the corresponding file on
  12.         the destination disk, or if no file by that name exists
  13.         on the destination disk, the user is asked whether that
  14.         file should be copied.  A .BAT file of the necessary
  15.         commands is written, and the requested copies are
  16.         performed by a copy of the command line interpreter
  17.         specified by the environment variable COMSPEC.
  18.  
  19.         The default source is current drive.  The default
  20.         destination is a compile time option (currently c:). 
  21.         If no default destination is #defined, executing the
  22.         program with an empty command line will cause a usage
  23.         message to be printed.
  24.  
  25.         The source and/or destination can be a pathname, and
  26.         the source can include a generic file name.
  27.  
  28.     options...
  29.         -b   make batch file $$.bat but don't execute it
  30.         -t   today's files - copy only files less than 24 hours old
  31.         -q   quiet - don't ask for confirmation
  32.         -r   recursive - visit subdirectories recursively
  33.         -u   update - don't copy new files
  34.  
  35.         Options can appear anywhere on the command line, and
  36.         can be combined, as in:
  37.                 update  -tu  i:  c:
  38.  
  39.     notes...
  40.         Requires getargs() (see file GETARGS.C in the IBM-PC
  41.         library), and exec() from the library supplied with
  42.         the DeSmet compiler.  exec() takes two arguments, each
  43.         a pointer to a string.  The first string is the
  44.         pathname of a program to be executed.  The second
  45.         string is the rest of the command line that program
  46.         gets.  For example,
  47.                      exec("a:command.com","/cdir *.*")
  48.         uses a subsidiary copy of the command line interpreter
  49.         to print out a directory.
  50.         
  51.         The source file was created with tabs set every four
  52.         columns.
  53.  
  54.     author...
  55.         James R. Van Zandt    (jrv@mitre-bedford)
  56.  
  57.     revisions...
  58.         James R.  Van Zandt VERSION 1.10, 15 Nov 86 
  59.             Generic file names are allowed in source, and pathnames,
  60.             with or without trailing '\', are allowed in either source
  61.             or destination.  Volume labels are never copied.  Updates
  62.             can be to a specified subdirectory on the same disk.
  63.         James A. Mullens    (jcm @ ornl-msr.arpa)   VERSION 1.11, Jan 86
  64.             Bug and Syntax Cleaning
  65.             The new attribute byte specifies that system and hidden
  66.             files are always eligible for updating.
  67.             (To restrict to "normal" files only, pass 0.)
  68.             Empty command line results in usage message.
  69.         James R.  Van Zandt VERSION 1.13, 6 Feb 87
  70.             Destination directory can be created if needed.
  71.             Warning printed if a file in the destination directory
  72.             is newer.
  73.         James R.  Van Zandt VERSION 2.00, 7 Feb 87
  74.             Recursive option added.
  75. */
  76.  
  77. #define VERSION "2.00"
  78. /*
  79.     If the following macro is defined, executing update with an empty
  80.     command line will copy new/updated files to the current directory
  81.     on the designated drive.  If it is not defined, then executing
  82.     update with an empty command line will result in a usage message
  83.     (with mention of a default drive omitted).
  84. */
  85. #define DEFAULT_DRIVE "c:"
  86.  
  87. #include <stdio.h>
  88.  
  89. /*---------------------------------------------------------------------------*/
  90. /*    typedefs and defines needed for getargs    */
  91.  
  92. #define INTEGER        0
  93. #define BOOLEAN        1
  94. #define CHARACTER    2
  95. #define STRING        3
  96. #define PROC        4
  97.  
  98. typedef struct
  99. {    unsigned    arg            ; /* command line switch */
  100.     unsigned    type        ; /* variable type (of those #defined above) */
  101.     int            *variable    ; /* pointer to variable */
  102.     char        *errmsg        ; /* pointer to error message */
  103. } ARG;
  104.  
  105. typedef struct
  106. {    char    fi_resv[21];    /* bytes 0-20    reserved by DOS            */
  107.     char    fi_attrib;        /* byte  21        File attribute            */
  108.     long    fi_time;        /* bytes 22-23    Create/update time        */
  109.                             /* bytes 24-25    create/update date        */
  110.     long    fi_fsize;        /* bytes 26-29    file size in bytes        */
  111.     char    fi_name[13];    /* bytes 30-42    file name & extension    */
  112. }
  113. FILE_INFO;
  114.  
  115. #define SUBDIR        0x10
  116. #define READONLY    0x01
  117. #define HIDDEN        0x02
  118. #define SYSTEM        0x04
  119. #define VOLUMELABEL    0x08
  120. #define ARCHIVED    0x20
  121.  
  122. #define IS_SUBDIR(p)    ((p).fi_attrib & SUBDIR            )
  123. #define IS_READONLY(p)    ((p).fi_attrib & READONLY            )
  124. #define IS_HIDDEN(p)    ((p).fi_attrib & HIDDEN            )
  125. #define IS_SYSTEM(p)    ((p).fi_attrib & SYSTEM            )
  126. #define IS_VOLUMELABEL(p)    ((p).fi_attrib & VOLUMELABEL    )
  127. #define IS_ARCHIVED(p)    ((p).fi_attrib & ARCHIVED            )
  128.  
  129. extern unsigned _rax,_rbx,_rcx,_rdx,_rsi,_rdi,_res,_rds,_doint();
  130. extern char _carryf,_zerof;
  131.  
  132. /*--------------------------------------------------------------------*/
  133. /*        directory related BDOS function numbers                        */
  134.  
  135. #define FINDFIRST    0x4e
  136. #define FINDNEXT    0x4f
  137. #define SETDTA        0x1a
  138. #define GETDTA        0x2f
  139. #define MAXFILES     200            /* maximum number of files to check        */
  140. #define OPEN        0x3d        /* dos function call to open a file        */
  141. #define CLOSE        0x3e        /* dos function call to close a file    */
  142. #define DATETIME    0x57        /* " to get/set file's date & time        */
  143.  
  144. #define BATFILE        "$$.BAT"    /* output file with copy commands        */
  145. #define DEFTIME        0            /* default time if file doesn't exist    */
  146.  
  147. char source_file[40], source_pattern[40],source_drive[40]="";
  148. char dest_file[40];
  149. #ifdef DEFAULT_DRIVE
  150. char dest_drive[40]=DEFAULT_DRIVE;
  151. #else
  152. char dest_drive[40];
  153. #endif
  154. char filepattern[40]="*.*";
  155. char *(filev[MAXFILES]);    /* pointers to names of source files        */
  156. long time[MAXFILES];        /* creation time/date for source files        */
  157. int filec=0;    /* number of files to be checked on destination disk    */
  158. int today=0;    /* nonzero if only today's files are to be checked        */
  159. int quiet=0;    /* nonzero if user isn't to be asked to confirm            */
  160. int recursive=0; /* nonzero if subdirectories are to be updated too        */
  161. int updated_only=0;    /* nonzero if user wants only updated files copied  */
  162. int batchfile_only=0;    /* zero if user wants batchfile executed */
  163. long cutoff;    /* only files newer than this are to be checked            */
  164. int copying=0;    /* number of files being copied                            */
  165. FILE *out;
  166.  
  167. /*---------------------------------------------------------------------------
  168.     The time field is a 32 bit long consisting of the date
  169.     and time fields returned from a DOS 0x57 call.  The date and time
  170.     are concataneted with the date in the most significant 16 bits and
  171.     the time in the least significant.  This way they can be compared
  172.     as a single number.
  173. */
  174.  
  175. /*---------------------------------------------------------------------------*/
  176.  
  177. long    gtime( file )    char *file;
  178. {
  179.     /*    Return the time and date for a file.
  180.  
  181.         The DOS time and date are concatenated to form one large
  182.         number.  Note that the high bit of this number will be set to 1
  183.         for all dates after 2043, which will cause the date comparisons
  184.         done in make() to fail.  THIS ROUTINE IS NOT PORTABLE (because
  185.         it assumes a 32 bit long).
  186.     */
  187.     short    handle    =    0    ;    /* place to remember file handle */
  188.     long    time            ;
  189.  
  190.     _rds=-1;
  191.     _rax=(OPEN<<8)|0;                /* open the file */
  192.     _rdx=(short) file;
  193.     _doint(0x21);
  194.     if(_carryf) return DEFTIME;        /* file doesn't exist */
  195.     handle=_rbx=_rax;
  196.     _rax=(DATETIME<<8)|0;            /* get the time */
  197.     _doint(0x21);
  198.     if(_carryf) err("DOS returned error from date/time request");
  199.     time=(((long)(_rdx))<<16) | ((long)(_rcx)&0xffffL);
  200.     _rax=CLOSE<<8;                    /* close the file */
  201.     _doint(0x21);
  202.     if(_carryf) err("DOS returned error from file close request");
  203.     return time;
  204. }
  205.  
  206. err(s) char *s;
  207. {    fprintf(stderr,s);
  208.     exit(1);
  209. }
  210.  
  211. find_first(filespec, attributes)
  212. char *filespec; int attributes;
  213. {    /*    Get directory information for the indicated file.
  214.         Ambiguous file references are okay but you have to use
  215.         find_next to get the rest of the file references.
  216.         In this case, the regs structure used by find_first
  217.         must be passed to find_next. 0 is returned on success,
  218.         otherwise the DOS error code is returned.                */
  219.  
  220.     _rds=-1;
  221.     _rdx=(short)filespec;
  222.     _rcx=attributes;
  223.     doscall(FINDFIRST);
  224.     return(_rax);
  225. }
  226. /*----------------------------------------------------------------------*/
  227.  
  228. find_next(t) char *t;
  229. {    /*    Get the next file in a ambiguous file reference.
  230.         A call to this function must be preceded by a
  231.         find_first call. The regp argument must be the
  232.         same register image used by the find_first call.
  233.         0 is returned on success, otherwise the error code
  234.         generated by DOS is returned.                    */
  235.  
  236.     _rds=-1;
  237.     _rdx=t;
  238.     doscall(FINDNEXT);
  239.     return(_rax);
  240. }
  241.  
  242. /*----------------------------------------------------------------------*/
  243.  
  244. doscall(id)
  245. {    /*    Do the DOS system call specified by "id"                        */
  246.     _rax=id<<8;
  247.     _doint(33);
  248. }
  249.  
  250. copy(s,d) char *s,*d;
  251. {    printf(" COPYING.");
  252.     fprintf(out,"copy %s %s\n",s,d);
  253.     copying++;
  254. }
  255.  
  256. create(s) char *s;
  257. {    printf(" CREATING.");
  258.     fprintf(out,"md %s\n", s);
  259.     copying++;
  260. }
  261.  
  262. yes()
  263. {    while(1)
  264.         {switch (getchar())
  265.             {case 'n':
  266.             case 'N':    return 0;
  267.             case 'y':
  268.             case 'Y':    return 1;
  269.             }
  270.         }
  271. }
  272.  
  273. envsearch(target,value) char *target,*value;
  274. {    char buf[256],*s,t[25],*env;
  275.     int nt;
  276.  
  277.     s=t;
  278.     while(*target) *s++=toupper(*target++);
  279.     *s++= '='; *s=0;
  280.     nt = strlen(t);
  281.     _lmove(2,44,_showcs()-0x10,&env,_showds());
  282.     _lmove(256,0,env,buf,_showds());
  283.     s=buf;
  284.     while(*s)
  285.         {/* printf("examining entry: %s \n",s); */
  286.         if (strncmp(t,s,nt))                    /* strings differ */
  287.             while(*s++) ;
  288.         else return (strcpy(value,s+nt));
  289.         }
  290.     *value=0;    /* no value found */
  291. }
  292.  
  293. help()
  294. {    printf("update   ver %s - move new/altered files to another disk \n",
  295.     VERSION);
  296.     puts("usage:   update [options] <source> <destination> \n");
  297.     puts("         update [options] <destination> \n");
  298. #ifdef DEFAULT_DRIVE
  299.     puts("         update [options]  \n");
  300.     puts("Default source is current drive \n");
  301.     printf("Default destination is %s \n", DEFAULT_DRIVE);
  302. #else
  303.     puts("Default source is current drive \n");
  304. #endif
  305.     puts("Source and/or destination can be a pathname.\n");
  306.     puts("Source can include a generic file name.\n\n");
  307.     puts("Options are:\n");
  308.     puts("    -b   make batch file $$.bat but don\'t execute it\n");
  309.     puts("    -t   today's files - copy only files less than 24 hours old\n");
  310.     puts("    -q   quiet - don't ask for confirmation\n");
  311.     puts("    -r   recursive - repeat for subdirectories\n");
  312.     puts("    -u   update - copy only altered files, not new ones\n");
  313.     puts("example: update  -tu  i:  c:archives\\\n");
  314.  
  315.     exit(0);
  316. }
  317.  
  318. version()    /* return MS-DOS version number. Version 2.01 returned as 201 */
  319. {    extern unsigned _rax;
  320.     _rax=0x3000;
  321.     _doint(0x21);
  322.     return ( (_rax&0xff)<<8 | (_rax&0xff00)>>8 );
  323. }
  324.  
  325. ARG Argtab[]={    {'b',    BOOLEAN,    &batchfile_only,"make $$.BAT but don\'t execute"}
  326.                 {'t',    BOOLEAN,    &today,            "today's files only"},
  327.                 {'q',    BOOLEAN,    &quiet,            "quiet"}
  328.                 {'r',    BOOLEAN,    &recursive,        "recursive"}
  329.                 {'u',    BOOLEAN,    &updated_only,    "copy updated files only"}
  330.             };
  331.  
  332. #define TABSIZE (sizeof(Argtab)/sizeof(ARG))
  333. /*--------------------------------------------------------------------------*/
  334. static FILE_INFO    info        ; /* DOS puts dirs here        */
  335.  
  336. main( argc, argv )
  337. int argc;
  338. char **argv;
  339. {    char *filename, command[40], ch, *r, *s, *t, *u;
  340.     int error, i;
  341.  
  342.     if(
  343. #ifndef DEFAULT_DRIVE
  344.         argc<=1 ||
  345. #endif    
  346.         (argc>1 && strcmp(argv[1],"?")==0)) help();    
  347.     argc=getargs(argc,argv,Argtab,TABSIZE);
  348.     if(argc>2)
  349.                         /* update <source drive> <destination drive>    */
  350.  
  351.         {strcpy(dest_drive,argv[2]);
  352.         s=t=argv[1];
  353.         while(*t) t++;
  354.         u=t-1;        /* u points to last char in source path  */
  355.         while(t>=s && *t!='\\' && *t!=':') t--;
  356.         if(t>=s)        /* '\' or ':' was found... pathname was specified */
  357.             {r=source_drive;
  358.             while(s<=t) *r++=*s++;
  359.             *r=0;
  360.             }
  361.         if(t<u)  /* characters follow the last '\' */
  362.             {if(index(t+1,'*') || index(t+1,'?'))
  363.                 {
  364. /*
  365. printf("wild cards are included, so\n");
  366. */
  367.                 goto gfn;
  368.                 }
  369.             _rds=-1;
  370.             _rdx=(char *)&info;    /* change the Disk Transfer Addr    */
  371.             doscall(SETDTA);    /* to point at info structure        */
  372.             error=find_first(argv[1],0x16);   /* find subdirectories
  373.                                or other files but not volume labels */
  374.             if(error)
  375.                 {printf("can\'t find %s - %s\n",argv[1],
  376.                     (error==2)?"not a legal path name":
  377.                         ((error==18)?"no such file":
  378.                             ("unknown error code")
  379.                         )
  380.                     );
  381.                 exit(1);
  382.                 }
  383.             if(IS_SUBDIR(info))
  384.                 {
  385. /*
  386. printf("%s is a subdirectory\n",argv[1]);
  387. */
  388.                 strcpy(source_drive,argv[1]);
  389.                 strncat(source_drive,"\\", 40);
  390.                 }
  391.             else
  392.                 {
  393. gfn:            
  394. /*
  395. printf("%s is not a subdirectory\n",argv[1]);
  396. */
  397.                 strcpy(filepattern,t+1);    /* file name was specified */
  398.                 }
  399.             }
  400.         }
  401.     else if(argc>1)
  402.                                 /* "update <destination drive>"    */
  403.  
  404.         {strcpy(dest_drive,argv[1]);
  405.         }
  406. /*
  407. printf(" source pathname = \"%s\",",source_drive);
  408. printf(" source filepattern = \"%s\"\n",filepattern);
  409. */
  410. /*
  411. printf(" destination pathname = \"%s\"\n",dest_drive);
  412. */
  413.  
  414.     if(!(out=fopen(BATFILE,"w")))
  415.         err("can't open output file");
  416.     if(today)
  417.         cutoff=gtime(BATFILE)-0x10000;    /* 24 hours ago */
  418.     else
  419.         cutoff=0;
  420.     _rds=-1;
  421.     _rdx=(char *)&info;    /* change the Disk Transfer Addr    */
  422.     doscall(SETDTA);    /* to point at info structure        */
  423.     update(source_drive, dest_drive);
  424.     fclose(out);
  425.     if(copying && !batchfile_only)
  426.         {if(version()<0x200)
  427.             {puts(
  428.             "DOS version 2.00 or higher required for automatic copying \n");
  429.             fallback();
  430.             }
  431.         envsearch("comspec",command);
  432.         if(find_first(command,0))        /* COMMAND.COM is missing */
  433.             {printf("%s is missing. \n",command);
  434.             fallback();
  435.             }
  436.         exec(command,strcat("/c\000                               ",BATFILE));
  437.         }
  438.     if(!batchfile_only) unlink(BATFILE);
  439. }
  440.  
  441. fallback()
  442. {    puts("Please enter these commands... \n    $$ \n    erase $$.bat \n ");
  443.     exit(0);
  444. }
  445.  
  446. update(source_drive, dest_drive) char *source_drive, *dest_drive;
  447. {    int error, i, dest_existed, subc;
  448.     char ch, *s, **subv, *s_file, *s_drive, *d_file, *d_drive;
  449.     long  td;
  450.     filec=0;
  451. #ifdef DEBUG
  452. printf("update(%s,%s)\n",source_drive,dest_drive);
  453. #endif
  454.     strcpy(source_pattern, source_drive); /* start with source pathname */
  455.     strncat(source_pattern, filepattern, 40); /* add "*.*" or user's pattern */
  456.     error=find_first(source_pattern, 6);
  457.     while(!error)
  458.         {
  459.         if(strcmp(info.fi_name,BATFILE)  /* we never copy our own .BAT file */
  460.         && info.fi_time>cutoff )
  461.             {if(!(filev[filec]=malloc(strlen(info.fi_name)+1)))
  462.                 err("can't allocate buffer space");
  463.             strcpy(filev[filec],info.fi_name);
  464.             time[filec]=info.fi_time;
  465.             filec++;
  466.             if(filec>=MAXFILES)
  467.                 {fprintf(stderr,"too many files...checking only the first %d \n",
  468.                 MAXFILES);
  469.                 break;
  470.                 }
  471.             }
  472.         error=find_next(source_pattern);
  473.         }
  474.     s=dest_drive+strlen(dest_drive)-1;
  475.     if(*s=='\\') *s=0;
  476.     dest_existed=is_directory(dest_drive);
  477.     if(dest_existed==2)
  478.         {printf("subdirectory name %s conflicts with existing file \
  479. in destination directory\n"
  480.         ,source_drive);
  481.         return;
  482.         }
  483.     if( !dest_existed )
  484.         {printf("destination directory %s doesn't exist", dest_drive);
  485.         if(quiet||(puts("     create it? "), yes()))
  486.             {create(dest_drive);
  487.             putchar('\n');
  488.             }
  489.         else
  490.             {putchar('\n');
  491.             return;
  492.             }
  493.         }
  494.     ch=dest_drive[strlen(dest_drive)-1];
  495.     if(ch!=':' && ch!='\\')
  496.         strncat(dest_drive,"\\", 40);
  497.     for(i=0; i<filec; i++)
  498.         {strcpy(source_file,source_drive);
  499.         strncat(source_file, filev[i], 40);
  500.         strcpy(dest_file,dest_drive);
  501.         strncat(dest_file, filev[i], 40);
  502.         if(dest_existed) td=gtime(dest_file);
  503.         else td=0;
  504.         if(td<time[i])
  505.             {if (td || !updated_only)
  506.                 {printf("%-15s  %12s",
  507.                     filev[i],td?"updated":"didn't exist");
  508.                 if(quiet||(puts("   copy it? "),yes()))
  509.                     copy(source_file,dest_file);
  510.                 putchar('\n');
  511.                 }
  512.             }
  513.         else if(td>time[i])
  514.             {printf("%-15s  WARNING: file in destination directory is newer!\n",
  515.                 filev[i]);
  516.             }
  517.         free(filev[i]);
  518.         }
  519.     if(!recursive) return;
  520. /*
  521.     record the names of all the subdirectories
  522. */
  523.     filec=0;
  524.     strcpy(source_pattern, source_drive);   /* start with source pathname */
  525.     ch=source_pattern[strlen(source_pattern)-1];
  526.     if(ch!=':' && ch!='\\')
  527.         strncat(source_pattern,"\\", 40);   /* add "\" if needed */
  528.     strncat(source_pattern, "*.*", 40);     /* add "*.*" */
  529.     error=find_first(source_pattern, 0x10); /* look for subdirectories */
  530.     while(!error)
  531.         {if(IS_SUBDIR(info) && strcmp(info.fi_name,".") && 
  532.         strcmp(info.fi_name,".."))
  533.             {if(!(filev[filec]=malloc(strlen(info.fi_name)+1)))
  534.                 err("can't allocate buffer space");
  535.             strcpy(filev[filec],info.fi_name);
  536.             filec++;
  537.             if(filec>=MAXFILES)
  538.                 {fprintf(stderr,
  539. "too many subdirectories...checking only the first %d \n",
  540.                 MAXFILES);
  541.                 break;
  542.                 }
  543.             }
  544.         error=find_next(source_pattern);
  545.         }
  546.     if(!filec) return;
  547. /*
  548.     make local copies of everything, lest it be overwritten
  549.     during the recursive call to update
  550. */
  551.     s_file=malloc(40);
  552.     s_drive=malloc(40);
  553.     d_file=malloc(40);
  554.     d_drive=malloc(40);
  555.     subv=malloc(filec*sizeof(filev[0]));
  556.     if(!subv || !s_file || !s_drive || !d_file || !d_drive) 
  557.         err("can't allocate buffer space");
  558.     subc=filec;
  559.     strcpy(s_drive, source_drive);
  560.     strcpy(d_drive, dest_drive);
  561.     for (i=0; i<subc; i++) subv[i]=filev[i];
  562. #ifdef DEBUG
  563. printf("found subdirectories:");
  564. for (i=0; i<subc; i++) printf("%s ", subv[i]);
  565. putchar('\n');
  566. #endif
  567.     for(i=0; i<subc; i++)
  568.         {strcpy(s_file, s_drive);
  569.         strncat(s_file, subv[i], 40);
  570.         ch=s_file[strlen(s_file)-1];
  571.         if(ch!=':' && ch!='\\')
  572.             strncat(s_file,"\\", 40);
  573.         strcpy(d_file, d_drive);
  574.         strncat(d_file, subv[i], 40);
  575.         update(s_file, d_file);
  576.         }
  577.     free(s_file); free(s_drive); free(d_file); free(d_drive); free(subv);
  578. }
  579.  
  580. is_directory(d) char *d;
  581. {    char *s;
  582.     int error;
  583.     if(d[1]==':') s=d+2;  /* skip drive identifier if any */
  584.     else s=d;
  585.     if(*s=='\\') s++;   /* skip leading '\' if any */
  586.     if(*s)  /* something else in path...must be specifying a subdirectory */
  587.         {
  588.         /* printf("is_directory() checking destination <%s>\n", d); */
  589.         error=find_first(d, 0x10);    /* look for subdirectories */
  590.         /* printf("    ...find_first() returns %d\n", error); */
  591.         if(error) return 0;
  592.         if (IS_SUBDIR(info)) return 1;  /* it's a subdirectory */
  593.         return 2;    /* it's a normal file */
  594.         }
  595.     return 1;    /* current directory on destination drive always exists */
  596. }
  597.